package com.stone.inventory.sharding.config.resolver;

import com.stone.inventory.sharding.config.properties.DbShardingProperty;
import com.stone.inventory.sharding.config.properties.InvRedisProperty;
import com.stone.inventory.sharding.config.properties.TableRuleProperty;
import com.stone.inventory.sharding.helper.ShardingHelper;
import com.stone.inventory.sharding.id.TableIdGenerator;
import com.stone.inventory.sharding.infra.algorithm.DefaultComplexKeysShardingAlgorithm;
import com.stone.inventory.sharding.infra.algorithm.ShardingAlgorithm;
import com.stone.inventory.sharding.infra.constant.ShardingConstants;
import com.stone.starter.redis.client.RedisShardingClient;
import io.shardingsphere.api.config.rule.ShardingRuleConfiguration;
import io.shardingsphere.api.config.rule.TableRuleConfiguration;
import io.shardingsphere.api.config.strategy.ComplexShardingStrategyConfiguration;
import io.shardingsphere.api.config.strategy.NoneShardingStrategyConfiguration;
import io.shardingsphere.api.config.strategy.ShardingStrategyConfiguration;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 分片属性解析器
 *
 * @author Mr_wenpan@163.com 2019-07-26
 */
public class ShardingResolver {

    /**
     * 通过配置数据构建sharding-jdbc分片配置数据
     *
     * @param shardingProperty 分片属性
     * @param shardingHelper   分片助手
     * @return sharding-jdbc 分片配置数据
     */
    public static ShardingRuleConfiguration parseShardingRuleConfiguration(DbShardingProperty shardingProperty,
                                                                           ShardingHelper shardingHelper) {

        ShardingRuleConfiguration shardingRuleConfiguration = new ShardingRuleConfiguration();

        // 默认数据库 main
        shardingRuleConfiguration.setDefaultDataSourceName(ShardingConstants.DataSource.MAIN);

        // 数据库不分片
        shardingRuleConfiguration.setDefaultDatabaseShardingStrategyConfig(new NoneShardingStrategyConfiguration());

        List<TableRuleConfiguration> tableRuleConfigurations = new ArrayList<>();

        // 分表规则添加
        shardingProperty.getTableRules().forEach((tableName, tableRule) -> tableRuleConfigurations
                .add(parseTableRuleConfiguration(tableName, tableRule, shardingHelper)));
        shardingRuleConfiguration.setTableRuleConfigs(tableRuleConfigurations);

        return shardingRuleConfiguration;
    }

    /**
     * 构建分表配置对象
     *
     * @param logicTable 逻辑表名称
     * @return 分表配置对象
     */
    private static TableRuleConfiguration parseTableRuleConfiguration(String logicTable,
                                                                      TableRuleProperty tableRuleProperty,
                                                                      ShardingHelper shardingHelper) {

        TableRuleConfiguration tableRuleConfiguration = new TableRuleConfiguration();
        tableRuleConfiguration.setLogicTable(logicTable);
        tableRuleConfiguration.setKeyGeneratorColumnName(tableRuleProperty.getIdColumnName());
        if (CollectionUtils.isNotEmpty(tableRuleProperty.getActualTables())) {
            StringBuilder actualNodesBuilder = new StringBuilder();
            for (String actualTable : tableRuleProperty.getActualTables()) {
                actualNodesBuilder.append(ShardingConstants.DataSource.MAIN)
                        .append(".").append(actualTable).append(",");
            }
            actualNodesBuilder.deleteCharAt(actualNodesBuilder.length() - 1);
            tableRuleConfiguration.setActualDataNodes(actualNodesBuilder.toString());
        }
        // 表分片策略
        ShardingAlgorithm shardingAlgorithm = shardingHelper.getShardingAlgorithmMap().get(logicTable);
        String shardingColumns = tableRuleProperty.getIdColumnName() + "," + tableRuleProperty.getShardingColumnName();
        DefaultComplexKeysShardingAlgorithm complexKeysShardingAlgorithm =
                new DefaultComplexKeysShardingAlgorithm(tableRuleProperty.getIdColumnName(), tableRuleProperty.getShardingColumnName(), shardingAlgorithm);
        ShardingStrategyConfiguration tableShardingStrategyConfig =
                new ComplexShardingStrategyConfiguration(shardingColumns, complexKeysShardingAlgorithm);
        tableRuleConfiguration.setTableShardingStrategyConfig(tableShardingStrategyConfig);
        return tableRuleConfiguration;
    }

    /**
     * 库存redis连接配置redisShardingClientsFactory
     *
     * @param invRedisProperty    库存redis分片配置数据
     * @param redisShardingClient redis连接工厂
     */
    public static void parseRedisConnect(InvRedisProperty invRedisProperty, RedisShardingClient redisShardingClient) {

        // 从Redis分片客户端中获取已注入的Redis连接工厂
        Map<String, RedisConnectionFactory> redisConnectionFactories = redisShardingClient.getRedisConnectionFactories();
        // 获取配置的redis实例
        invRedisProperty.getInstances().forEach((instanceKey, instanceProperties) -> {
            // 如果原有的redisConnectionFactories已经注册了
            if (redisConnectionFactories.containsKey(instanceKey)) {
                throw new RuntimeException("redis instance [" + instanceKey + "] configuration conflict");
            }
            // 创建Redis连接池信息
            GenericObjectPoolConfig redisPoolConfig = new GenericObjectPoolConfig();
            redisPoolConfig.setMinIdle(instanceProperties.getPoolMinIdle());
            redisPoolConfig.setMaxIdle(instanceProperties.getPoolMaxIdle());
            redisPoolConfig.setMaxTotal(instanceProperties.getPoolMaxIdle());
            redisPoolConfig.setMaxWaitMillis(instanceProperties.getPoolMaxWaitMillis());
            // 构建Redis客户端连接配置
            LettuceClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder()
                    .poolConfig(redisPoolConfig)
                    .commandTimeout(Duration.ofMillis(instanceProperties.getTimeoutMillis()))
                    .build();
            // 根据Redis客户端连接配置构建新的连接工厂
            LettuceConnectionFactory lettuceConnectionFactory = null;
            if (StringUtils.isEmpty(instanceProperties.getMaster())) {
                RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
                redisStandaloneConfiguration.setHostName(instanceProperties.getHost());
                redisStandaloneConfiguration.setPassword(RedisPassword.of(instanceProperties.getPassword()));
                redisStandaloneConfiguration.setPort(instanceProperties.getPort());
                redisStandaloneConfiguration.setDatabase(instanceProperties.getDbIndex());
                lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration, lettuceClientConfiguration);
            }
            assert lettuceConnectionFactory != null;
            lettuceConnectionFactory.afterPropertiesSet();
            // 将连接工厂添加到Redis分片客户端中保存
            redisShardingClient.addRedisConnectionFactories(instanceKey, lettuceConnectionFactory);
            // 根据连接工厂创建该Redis实例的redisTemplate
            StringRedisTemplate redisTemplate = new StringRedisTemplate(lettuceConnectionFactory);
            redisShardingClient.addRedisTemplates(instanceKey, redisTemplate);
        });

        // 校验所有映射都有对应的redis实例
        invRedisProperty.getShardingMapping().forEach((logicTable, redisInstance) -> {
            if (!redisConnectionFactories.containsKey(redisInstance)) {
                throw new RuntimeException("not found redis instance for redis mapping " + redisInstance);
            }
        });

        // 默认的redis校验
        if (invRedisProperty.getShardingMapping().isEmpty()) {
            // 库存分片的默认redis实例（即不分片的时候使用）
            if (!redisConnectionFactories.containsKey(invRedisProperty.getDefaultInstance())) {
                throw new RuntimeException("not found redis instance for redis mapping " + invRedisProperty.getDefaultInstance());
            }
        }

        // 缓存的redis校验
        if (!redisConnectionFactories.containsKey(invRedisProperty.getCacheInstance())) {
            throw new RuntimeException("not found redis instance for redis mapping " + invRedisProperty.getCacheInstance());
        }
    }

    /**
     * 构建表Id生成器
     *
     * @param dbShardingProperty 数据库分片配置
     * @return id生成器
     */
    public static List<TableIdGenerator> parseTableIdGenerators(DbShardingProperty dbShardingProperty) {

        List<TableIdGenerator> tableIdGenerators = new ArrayList<>();
        dbShardingProperty.getTableRules().forEach((logicTable, tableRuleProperty) -> {
            long index = 0L;
            for (String actualTable : tableRuleProperty.getActualTables()) {
                TableIdGenerator tableIdGenerator =
                        new TableIdGenerator(index, actualTable, dbShardingProperty.getShardingIdRule());
                tableIdGenerators.add(tableIdGenerator);
                index++;
            }
        });
        return tableIdGenerators;
    }

}
